﻿/*!
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

using System;

namespace Pittypat
{
    /// <summary>
    /// 表示一个日期值。
    /// </summary>
    public struct @date : IPrimitive
    {
        /// <summary>
        /// 获取当前日期。
        /// </summary>
        public static @date Today
        {
            get
            {
                return DateTime.Today;
            }
        }

        /// <summary>
        /// 将指定的字符串解析为一个 @date 值。
        /// </summary>
        /// <param name="s">要解析的字符串。</param>
        /// <param name="provider">提供有关 s 的区域性特定格式信息。</param>
        /// <returns>一个 @date 或 null。</returns>
        public static @date? Parse(string s, IFormatProvider provider = null)
        {
            if (String.IsNullOrEmpty(s))
            {
                return null;
            }

            s = s.Trim();
            if (String.IsNullOrEmpty(s))
            {
                return null;
            }

            switch (s.ToLower())
            {
                case "min":
                case "minvalue":
                case "min_value":
                case "default":
                    return @date.MinValue;
                case "max":
                case "maxvalue":
                case "max_value":
                    return @date.MaxValue;
                case "today":
                case "now":
                case "current":
                    return @date.Today;
                default:
                    break;
            }

            DateTime value = DateTime.MinValue;
            if (provider != null)
            {
                if (DateTime.TryParse(s, provider, System.Globalization.DateTimeStyles.None, out value))
                {
                    return new @date(value);
                }
            }
            else
            {
                if (DateTime.TryParse(s, out value))
                {
                    return new @date(value);
                }
            }

            return null;
        }
        
        /// <summary>
        /// 计算指定日期加上指定天数后的新日期。 
        /// </summary>
        /// <param name="x">一个 @date 结构。</param>
        /// <param name="t">一个 Int32 结构。</param>
        /// <returns>新的日期。</returns>
        public static @date operator +(@date x, int t)
        {
            return new @date(x.value + t);
        }

        /// <summary>
        /// 计算两个日期之间相差的天数。
        /// </summary>
        /// <param name="x">第一个值。</param>
        /// <param name="y">第二个值。</param>
        /// <returns>两个日期之间相差的天数。</returns>
        public static int operator -(@date x, @date y)
        {
            return (x.value - y.value);
        }

        /// <summary>
        /// 比较两个值是否相等。
        /// </summary>
        /// <param name="x">第一个值。</param>
        /// <param name="y">第二个值。</param>
        /// <returns>如果两个值相等，则返回 true，否则返回 false。</returns>
        public static bool operator ==(@date x, @date y)
        {
            return x.value == y.value;
        }

        /// <summary>
        /// 比较两个值是否不相等。
        /// </summary>
        /// <param name="x">第一个值。</param>
        /// <param name="y">第二个值。</param>
        /// <returns>如果两个值不相等，则返回 true，否则返回 false。</returns>
        public static bool operator !=(@date x, @date y)
        {
            return x.value != y.value;
        }

        /// <summary>
        /// 比较两个值的大小。
        /// </summary>
        /// <param name="x">第一个值。</param>
        /// <param name="y">第二个值。</param>
        /// <returns>如果 x 大于 y，则返回 true，否则返回 false。</returns>
        public static bool operator >(@date x, @date y)
        {
            return x.value > y.value;
        }

        /// <summary>
        /// 比较两个值的大小。
        /// </summary>
        /// <param name="x">第一个值。</param>
        /// <param name="y">第二个值。</param>
        /// <returns>如果 x 大于等于 y，则返回 true，否则返回 false。</returns>
        public static bool operator >=(@date x, @date y)
        {
            return x.value >= y.value;
        }

        /// <summary>
        /// 比较两个值的大小。
        /// </summary>
        /// <param name="x">第一个值。</param>
        /// <param name="y">第二个值。</param>
        /// <returns>如果 x 小于 y，则返回 true，否则返回 false。</returns>
        public static bool operator <(@date x, @date y)
        {
            return x.value < y.value;
        }

        /// <summary>
        /// 比较两个值的大小。
        /// </summary>
        /// <param name="x">第一个值。</param>
        /// <param name="y">第二个值。</param>
        /// <returns>如果 x 小于等于 y，则返回 true，否则返回 false。</returns>
        public static bool operator <=(@date x, @date y)
        {
            return x.value <= y.value;
        }

        /// <summary>
        /// 表示 1000-1-1。
        /// </summary>
        public static readonly @date MinValue = new DateTime(1000, 1, 1);
        /// <summary>
        /// 表示 9999-12-31。
        /// </summary>
        public static readonly @date MaxValue = DateTime.MaxValue;

        private DateValue value;

        /// <summary>
        /// 初始化 @date 结构的新实例。
        /// </summary>
        /// <param name="year">年份。</param>
        /// <param name="month">月份。</param>
        /// <param name="day">日期。</param>
        public @date(int year, int month, int day)
        {
            this.value = new DateValue(year, month, day);
        }

        /// <summary>
        /// 初始化 @date 结构的新实例。
        /// </summary>
        /// <param name="value">结构的值。</param>
        public @date(DateTime value)
        {
            this.value = value;
        }

        internal @date(DateValue value)
        {
            this.value = value;
        }

        /// <summary>
        /// 获取一个值，该值指示此时间的年份是否是一个闰年。
        /// </summary>
        public bool IsLeapYear
        {
            get
            {
                return DateTime.IsLeapYear(this.value.Year);
            }
        }

        /// <summary>
        /// 获取当前日期所在的月份中总共有多少天。
        /// </summary>
        public int DaysInMonth
        {
            get
            {
                return DateTime.DaysInMonth(this.value.Year, this.value.Month);
            }
        }

        /// <summary>
        /// 获取年份。
        /// </summary>
        public int Year
        {
            get
            {
                return this.value.Year;
            }
        }

        /// <summary>
        /// 获取月份。
        /// </summary>
        public int Month
        {
            get
            {
                return this.value.Month;
            }
        }

        /// <summary>
        /// 获取日期。
        /// </summary>
        public int Day
        {
            get
            {
                return this.value.Day;
            }
        }

        /// <summary>
        /// 获取此日期值的刻度数。
        /// </summary>
        public int Ticks
        {
            get
            {
                return this.value.Ticks;
            }
        }

        /// <summary>
        /// 获取日期是星期几。
        /// </summary>
        public DayOfWeek DayOfWeek
        {
            get
            {
                return this.value.DayOfWeek;
            }
        }

        /// <summary>
        /// 获取日期是当前年份中的第几天（1 到 366 之间）。
        /// </summary>
        public int DayOfYear
        {
            get
            {
                return this.value.DayOfYear;
            }
        }

        /// <summary>
        /// 获取当前日期是当前年份中的第几周（星期一作为一周的第一天）。
        /// </summary>
        public int WeekOfYear1
        {
            get
            {
                // 当前年份第一天是星期几
                var dow = new DateTime(this.value.Year, 1, 1).DayOfWeek;
                int days = this.value.DayOfYear + (dow == System.DayOfWeek.Sunday ? 7 : ((int)dow - 1));
                if (days <= 7)
                {
                    return 1;
                }
                else
                {
                    if ((days % 7) == 0)
                    {
                        return days / 7;
                    }
                    else
                    {
                        return (days / 7) + 1;
                    }
                }
            }
        }

        /// <summary>
        /// 获取当前日期是当前年份中的第几周（星期天作为一周的第一天）。
        /// </summary>
        public int WeekOfYear
        {
            get
            {
                // 当前年份第一天是星期几
                int days = this.value.DayOfYear + (int)(new DateTime(this.value.Year, 1, 1).DayOfWeek);
                if (days <= 7)
                {
                    return 1;
                }
                else
                {
                    if ((days % 7) == 0)
                    {
                        return days / 7;
                    }
                    else
                    {
                        return (days / 7) + 1;
                    }
                }
            }
        }

        /// <summary>
        /// 获取当前日期处在当前年份的第几个季度。
        /// </summary>
        public int Quarter
        {
            get
            {
                switch (this.value.Month)
                {
                    case 1:
                    case 2:
                    case 3:
                        return 1;
                    case 4:
                    case 5:
                    case 6:
                        return 2;
                    case 7:
                    case 8:
                    case 9:
                        return 3;
                    case 10:
                    case 11:
                    case 12:
                        return 4;
                    default:
                        return 0;
                }
            }
        }

        /// <summary>
        /// 返回一个新的 @interval 值，此值是当前日期减去指定日期后的时间间隔。
        /// </summary>
        /// <param name="date">要减去的日期。</param>
        /// <returns>新的 @interval 值。</returns>
        public @interval Subtract(@date date)
        {
            DateTime d = (DateTime)this.value;
            return new @interval(d.Subtract(date));
        }

        /// <summary>
        /// 返回一个新的 @date 值，此值是当前日期减去指定时间间隔后的新日期。
        /// </summary>
        /// <param name="interval">要减去的时间间隔。</param>
        /// <returns>新的 @date 值。</returns>
        public @date Subtract(@interval interval)
        {
            DateTime d = (DateTime)this.value;
            return new @date(d.Subtract(interval));
        }

        /// <summary>
        /// 返回一个新的 date 值，此值是当前日期加上指定时间间隔后的新日期。
        /// </summary>
        /// <param name="interval">要添加的时间间隔。</param>
        /// <returns>新的 date 值。</returns>
        public @date Add(@interval interval)
        {
            DateTime d = (DateTime)this.value;
            return new @date(d.Add(interval));
        }

        /// <summary>
        /// 返回一个新的 date 值，此值是当前日期加上指定天数后的新日期。
        /// </summary>
        /// <param name="days">要添加的天数。</param>
        /// <returns>新的 date 值。</returns>
        public @date AddDays(int days)
        {
            DateTime d = (DateTime)this.value;
            return new date(d.AddDays(days));
        }

        /// <summary>
        /// 返回一个新的 date 值，此值是当前日期加上指定月数后的新日期。
        /// </summary>
        /// <param name="months">要添加的月数。</param>
        /// <returns>新的 date 值。</returns>
        public @date AddMonths(int months)
        {
            DateTime d = (DateTime)this.value;
            return new date(d.AddMonths(months));
        }

        /// <summary>
        /// 返回一个新的 date 值，此值是当前日期加上指定年数后的新日期。
        /// </summary>
        /// <param name="years">要添加的年数。</param>
        /// <returns>新的 date 值。</returns>
        public @date AddYears(int years)
        {
            DateTime d = (DateTime)this.value;
            return new date(d.AddYears(years));
        }

        /// <summary>
        /// 将当前值转换为 int 类型的值。
        /// </summary>
        /// <param name="value">要转换的值。</param>
        /// <returns>转换后的值。</returns>
        public static implicit operator DateTime(@date value)
        {
            return (DateTime)value.value;
        }

        /// <summary>
        /// 将指定的值转换为 @date 类型的值。
        /// </summary>
        /// <param name="value">要转换的值。</param>
        /// <returns>转换后的值。</returns>
        public static implicit operator @date(DateTime value)
        {
            return new @date(value);
        }

        /// <summary>
        /// 将指定的值转换为 @date 类型的值。
        /// </summary>
        /// <param name="value">要转换的值。</param>
        /// <returns>转换后的值。</returns>
        public static implicit operator @date(string value)
        {
            if (String.IsNullOrEmpty(value))
            {
                return MinValue;
            }

            value = value.ToLower().Trim();
            if (value == "min")
            {
                return MinValue;
            }

            if (value == "max")
            {
                return MaxValue;
            }

            if (value == "now" || value == "today" || value == "current")
            {
                return Today;
            }

            return new @date(DateTime.Parse(value));
        }

        /// <summary>
        /// 判断两个值是否相等。
        /// </summary>
        /// <param name="obj">要比较的值。</param>
        /// <returns>如果两个值相等，则返回 true，否则返回 false。</returns>
        public override bool Equals(object obj)
        {
            if (obj is @date)
            {
                return this.value == ((@date)obj).value;
            }

            if (obj is DateValue)
            {
                return this.value == (DateValue)obj;
            }

            if (obj is DateTime)
            {
                return this.value == (DateTime)obj;
            }

            return false;
        }

        /// <summary>
        /// 返回此值的哈希代码。
        /// </summary>
        /// <returns>此值的哈希代码。</returns>
        public override int GetHashCode()
        {
            return this.value.GetHashCode();
        }

        /// <summary>
        /// 返回此值的字符串表示形式。
        /// </summary>
        /// <returns>此值的字符串表示形式。</returns>
        public override string ToString()
        {
            return this.ToString(null);
        }

        #region IPrimitive Members

        object IPrimitive.Value
        {
            get
            {
                return (DateTime)this;
            }
        }

        /// <summary>
        /// 将当前对象作为 JSON 字符串写入指定的文本流。
        /// </summary>
        /// <param name="writer">目标文本流。</param>
        /// <exception cref="ArgumentNullException">writer 是 null。</exception>
        public void Write(System.IO.TextWriter writer)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }

            writer.Write("{{\"year\":{0},\"month\":{1},\"day\":{2}}}", this.value.Year, this.value.Month, this.value.Day);
        }

        #endregion

        #region IConvertible Members

        TypeCode IConvertible.GetTypeCode()
        {
            return TypeCode.Object;
        }

        bool IConvertible.ToBoolean(IFormatProvider provider)
        {
            return this.value != DateValue.MinValue;
        }

        byte IConvertible.ToByte(IFormatProvider provider)
        {
            return unchecked((byte)this.value.Ticks);
        }

        char IConvertible.ToChar(IFormatProvider provider)
        {
            return unchecked((char)this.value.Ticks);
        }

        DateTime IConvertible.ToDateTime(IFormatProvider provider)
        {
            return (DateTime)this.value;
        }

        decimal IConvertible.ToDecimal(IFormatProvider provider)
        {
            return unchecked((decimal)this.value.Ticks);
        }

        double IConvertible.ToDouble(IFormatProvider provider)
        {
            return unchecked((double)this.value.Ticks);
        }

        short IConvertible.ToInt16(IFormatProvider provider)
        {
            return unchecked((short)this.value.Ticks);
        }

        int IConvertible.ToInt32(IFormatProvider provider)
        {
            return unchecked((int)this.value.Ticks);
        }

        long IConvertible.ToInt64(IFormatProvider provider)
        {
            return unchecked((long)this.value.Ticks);
        }

        sbyte IConvertible.ToSByte(IFormatProvider provider)
        {
            return unchecked((sbyte)this.value.Ticks);
        }

        float IConvertible.ToSingle(IFormatProvider provider)
        {
            return unchecked((float)this.value.Ticks);
        }

        string IConvertible.ToString(IFormatProvider provider)
        {
            return this.value.ToString(provider);
        }

        object IConvertible.ToType(Type conversionType, IFormatProvider provider)
        {
            return ((IConvertible)this.value).ToType(conversionType, provider);
        }

        ushort IConvertible.ToUInt16(IFormatProvider provider)
        {
            return unchecked((ushort)this.value.Ticks);
        }

        uint IConvertible.ToUInt32(IFormatProvider provider)
        {
            return unchecked((uint)this.value.Ticks);
        }

        ulong IConvertible.ToUInt64(IFormatProvider provider)
        {
            return unchecked((ulong)this.value.Ticks);
        }

        #endregion

        #region IComparable Members

        int IComparable.CompareTo(object other)
        {
            if (other is @date)
            {
                return this.value.CompareTo(((@date)other).value);
            }

            return 1;
        }

        #endregion

        #region IFormattable Members

        /// <summary>
        /// 使用指定的格式格式化当前实例的值。
        /// </summary>
        /// <param name="format">指定要使用的格式的 String。</param>
        /// <param name="formatProvider">用于格式化该值的 IFormatProvider。null 表示从操作系统的当前区域设置中获取数字格式信息。</param>
        /// <returns>一个 String，包含具有指定格式的当前实例的值。</returns>
        public string ToString(string format, IFormatProvider formatProvider)
        {
            if (String.Equals(format, "Like", StringComparison.InvariantCultureIgnoreCase))
            {
                int days = DateValue.Today - this.value;
                string like = null;

                switch (days)
                {
                    case 0:
                        like = "今天";
                        break;
                    case -1:
                        like = "昨天";
                        break;
                    case -2:
                        like = "前天";
                        break;
                    case 1:
                        like = "明天";
                        break;
                    case 2:
                        like = "后天";
                        break;
                    default:
                        if (days < 0)
                        {
                            days = -days;
                            if (days < 28)
                            {
                                like = String.Format("{0} 天前", days);
                            }
                            else if (days < 33)
                            {
                                like = "一个月前";
                            }
                            else if (days < 340)
                            {
                                like = String.Format("{0} 个月前", Math.Round(days / 30d));
                            }
                            else if (days < 390)
                            {
                                like = "一年前";
                            }
                            else
                            {
                                like = String.Format("{0} 年前", Math.Round(days / 365d));
                            }
                        }
                        else
                        {
                            if (days < 28)
                            {
                                like = String.Format("{0} 天后", days);
                            }
                            else if (days < 33)
                            {
                                like = "一个月后";
                            }
                            else if (days < 340)
                            {
                                like = String.Format("{0} 个月后", Math.Round(days / 30d));
                            }
                            else if (days < 390)
                            {
                                like = "一年后";
                            }
                            else
                            {
                                like = String.Format("{0} 年后", Math.Round(days / 365d));
                            }
                        }
                        break;
                }

                return String.Format("{0}: {1}", like, this.value.ToString(format ?? "yyyy/MM/dd", formatProvider));
            }

            return this.value.ToString(format ?? "yyyy/MM/dd", formatProvider);
        }

        /// <summary>
        /// 使用指定的格式格式化当前实例的值。
        /// </summary>
        /// <param name="format">指定要使用的格式的 String。</param>
        /// <returns>一个 String，包含具有指定格式的当前实例的值。</returns>
        public string ToString(string format)
        {
            return this.ToString(format, null);
        }

        #endregion
    }
}
